﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace iBet
{
    public class Calculator
    {
        #region Private Properties
        private string ErrorMessage = string.Empty;
        #endregion

        #region Calculation Methods
        /// <summary>
        /// This method calculates ticket 
        /// </summary>
        /// <param name="ticket">ticket</param>
        /// <returns>ticket profit</returns>
        public double CalculateTicket(Ticket ticket)
        {
            if (!string.IsNullOrEmpty(ValidateTicket(ticket)))
                return 0.00;

            //get ticket items with odds that are hit
            List<TicketItem> ticketItemsHit = ticket.Items.Select(
                                                    p => new TicketItem()
                                                        {
                                                            SystemType = p.SystemType,
                                                            Odds = p.Odds.Where(t => t.IsHit).ToList()
                                                        }).ToList();

            //get the total number of combinations
            double totalCombinations = 1.0;
            foreach (TicketItem ticketItem in ticket.Items)
            {
                List<double> oddsList = ticketItem.Odds.Select(p => p.Value).ToList();
                Combinatorics.Combinations<double> combinations = new Combinatorics.Combinations<double>(oddsList, ticketItem.SystemType.Min, Combinatorics.GenerateOption.WithoutRepetition);
                totalCombinations *= combinations.Count;
            }

            //get all combinations from all ticket items
            List<Combinatorics.Combinations<double>> allCombinations = new List<Combinatorics.Combinations<double>>();
            foreach (TicketItem ticketItem in ticketItemsHit)
            {
                List<double> oddsList = ticketItem.Odds.Select(p => p.Value).ToList();
                Combinatorics.Combinations<double> combinations = new Combinatorics.Combinations<double>(oddsList, ticketItem.SystemType.Min, Combinatorics.GenerateOption.WithoutRepetition);
                allCombinations.Add(combinations);
            }

            double betPerCombination = ticket.Bet / totalCombinations;
            double totalProfit = 0.0;

            //Mulitply odds for each system
            List<List<Odd>> multipliedOdds = new List<List<Odd>>();
            foreach (Combinatorics.Combinations<double> combination in allCombinations)
            {
                List<Odd> multipliedOddsPerSystem = new List<Odd>();

                foreach (List<double> oddsList in combination)
                {
                    double productOdd = 1;
                    foreach (double odd in oddsList)
                    {
                        productOdd *= odd;
                    }
                    multipliedOddsPerSystem.Add(new Odd() { Value = productOdd, IsHit = true });
                }
                multipliedOdds.Add(multipliedOddsPerSystem);
            }

            //final calculations
            List<Odd> finalCalculatedOdds = GetOddsCombinations(multipliedOdds);

            //calculate the total profit of the ticket
            foreach (Odd finalCalculatedOdd in finalCalculatedOdds)
            {
                totalProfit += finalCalculatedOdd.Value * betPerCombination;
            }

            return totalProfit;
        }

        /// <summary>
        /// This method performs odds calculations
        /// </summary>
        /// <param name="tempProducts">list of odds for calculations</param>
        /// <returns>list of calculated odds</returns>
        private List<Odd> GetOddsCombinations(List<List<Odd>> products)
        {
            List<Odd> result = new List<Odd>();
            Odd odd = new Odd() { Value = 1.0, IsHit = true };
            result.Add(odd);

            List<Odd> tempResult = new List<Odd>();

            foreach (List<Odd> itemOdds in products)
            {
                int tempResultCount = result.Count;
                int tempItemOddsCount = itemOdds.Count;
                for (int i = 0; i < (tempResultCount * tempItemOddsCount); i++)
                {
                    odd = new Odd();
                    odd.IsHit = true;
                    odd.Value = result[((i - (i % tempItemOddsCount)) / tempItemOddsCount)].Value * itemOdds[i % tempItemOddsCount].Value;
                    tempResult.Add(odd);
                }
                result.Clear();
                result.AddRange(tempResult);
                tempResult.Clear();
            }

            return result;
        }
        #endregion

        #region Validation Methods
        /// <summary>
        /// This method performs validation on ticket
        /// If string.IsNullOrEmpty then ticket is valid
        /// </summary>
        /// <param name="ticket">ticket</param>
        /// <returns>error message</returns>
        public string ValidateTicket(Ticket ticket)
        {
            if (!IsDefined(ticket))
                return this.ErrorMessage;

            if (ticket.Items.Count == 0)
                return "Error 4";

            foreach (TicketItem ticketItem in ticket.Items)
            {
                if (!IsItemDefined(ticketItem))
                    return this.ErrorMessage;
            }

            return string.Empty;
        }

        /// <summary>
        /// This method checks if ticket item is defined
        /// </summary>
        /// <param name="ticketItem">ticket item</param>
        /// <returns>is defined</returns>
        private bool IsItemDefined(TicketItem ticketItem)
        {
            if (ticketItem.SystemType == null)
            {
                this.ErrorMessage = "Error 5";
                return false;
            }

            if (ticketItem.Odds == null)
            {
                this.ErrorMessage = "Error 6";
                return false;
            }

            if (ticketItem.Odds.Count == 0)
            {
                this.ErrorMessage = "Error 7";
                return false;
            }

            if (!IsItemValueValid(ticketItem))
                return false;

            return true;
        }

        /// <summary>
        /// This method checks if ticket item value is valid
        /// </summary>
        /// <param name="ticketItem">ticket item</param>
        /// <returns>is ticket item value valid</returns>
        private bool IsItemValueValid(TicketItem ticketItem)
        {
            if (ticketItem.SystemType.Min > ticketItem.SystemType.Max)
            {
                this.ErrorMessage = "Error 8";
                return false;
            }

            if (ticketItem.SystemType.Max != ticketItem.Odds.Count)
            {
                this.ErrorMessage = "Error 9";
                return false;
            }

            var hitOdds = from c in ticketItem.Odds
                          where c.IsHit
                          select c;

            if (hitOdds.Count() < ticketItem.SystemType.Min)
            {
                this.ErrorMessage = "Error 10";
                return false;
            }

            var negativeOdds = from c in ticketItem.Odds
                               where c.Value <= 0
                               select c;

            if (negativeOdds.Count() > 0)
            {
                this.ErrorMessage = "Error 11";
                return false;
            }

            return true;
        }

        /// <summary>
        /// checks if the ticket or his items are null
        /// </summary>
        /// <param name="ticket"></param>
        /// <returns></returns>
        private bool IsDefined(Ticket ticket)
        {
            // if the passed ticket is null
            if (ticket == null)
            {
                this.ErrorMessage = "Error 1";
                return false;
            }

            // if the attribute Items does not exist
            if (ticket.Items == null)
            {
                this.ErrorMessage = "Error 2";
                return false;
            }

            return true;
        }
        #endregion
    }
}
